package dnaSequencer;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;

public class HeurystykaNegatywne2 {
	boolean debug;
	boolean log;
	boolean random;

	private final int A = 0;
	private final int C = 1;
	private final int G = 2;
	private final int T = 3;
	
	private String fileName = null;
	private ArrayList<String> oligonukleotydy = null;
	private int singlePieceLength;
	private int totalLength;

	private ArrayList<DNAPiece>[] front = null;
	private ArrayList<DNAPiece>[] back = null;

	private ArrayList<DNAPiece> DNApieces = null;
	private int usedWords;

	public HeurystykaNegatywne2(String fileName, String params) {
		this.fileName = fileName;
		if (params != null) {
			if (params.contains("r"))
				random = true;
			else
				random = false;

			if (params.contains("l"))
				log = true;
			else
				log = false;

			if (params.contains("d"))
				debug = true;
			else
				debug = false;

		}else{
			log = false;
			debug = false;
			random = false;
		}
	}

	public void run() {
		long start, stop;
		load();
		start = System.currentTimeMillis();
		buildDNA();
		//insertCollidingDNA();
		pruneDNA();
		stop = System.currentTimeMillis();
		
		System.out.println("Czas " + (stop - start) + " ms");
	}

	private void load() {
		try {
			oligonukleotydy = ResultsParser.loadResultsFromFile(fileName);
			singlePieceLength = ResultsParser.getL();
			totalLength = ResultsParser.getN();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	private int countHashFront(DNAPiece oligonukleotyd, int length) {
		return countHash(oligonukleotyd.getDNA(), length - 1, 0);
	}

	private int countHashBack(DNAPiece oligonukleotyd, int length) {
		return countHash(oligonukleotyd.getDNA(), oligonukleotyd.getDNA()
				.length() - 1, oligonukleotyd.getDNA().length() - length);
	}

	private int countHash(String oligonukleotyd, int start, int finish) {
		int mnoznik = 1;
		int wartosc = 0;
		for (int i = start; i >= finish; i--) {
			char nukleotyd = oligonukleotyd.charAt(i);
			switch (nukleotyd) {
			case 'A':
				wartosc += A * mnoznik;
				break;
			case 'C':
				wartosc += C * mnoznik;
				break;
			case 'G':
				wartosc += G * mnoznik;
				break;
			case 'T':
				wartosc += T * mnoznik;
				break;
			}
			mnoznik *= 4;
		}
		return wartosc;

	}

	private void sortInArrays(ArrayList<DNAPiece> dane, int length) {
		front = new ArrayList[(int) Math.pow(4, length)];
		back = new ArrayList[(int) Math.pow(4, length)];

		for (DNAPiece a : dane) {
			int hashFront = countHashFront(a, length);
			int hashBack = countHashBack(a, length);
			if (front[hashFront] == null) {
				front[hashFront] = new ArrayList<DNAPiece>();
			}
			front[hashFront].add(a);
			if (back[hashBack] == null) {
				back[hashBack] = new ArrayList<DNAPiece>();
			}
			back[hashBack].add(a);
		}
	}

	private void show() {
		for (int i = 0; i < front.length; i++) {
			if (front[i] != null) {
				for (DNAPiece a : front[i])
					System.out.println(a.getDNA());
			}
		}
	}

	private void buildDNA() {
		ArrayList<DNAPiece> zbiorDanych = new ArrayList<DNAPiece>();

		for (String a : oligonukleotydy)
			zbiorDanych.add(new DNAPiece(singlePieceLength, a));

		int zakladka = singlePieceLength - 1;
		while (zbiorDanych.size() > 1 && zakladka > 0) {
			DNApieces = new ArrayList<DNAPiece>();
			if (debug || log)
				System.out.println("Zaczynam liczyc dla " + zakladka
						+ "(na starcie mam " + zbiorDanych.size() + " słów)");

			sortInArrays(zbiorDanych, zakladka);
			if (debug)
				System.out.println("Posortowałem tablice");

			Random rand = null;
			int startIndex = 0;
			int arrayStartIndex = 0;

			while (zbiorDanych.size() != 0) {
				DNAPiece tmpDNA = new DNAPiece(singlePieceLength);

				if (random) {
					rand = new Random(System.currentTimeMillis());
					startIndex = rand.nextInt(zbiorDanych.size());
				}

				tmpDNA.add(zbiorDanych.get(startIndex), DNAPiece.FRONT);
				zbiorDanych.remove(startIndex);

				// Doklejanie z tylu
				if (debug)
					System.out.println("Do tyłu");
				while (zbiorDanych.size() > 0) {
					if (debug)
						System.out.print(tmpDNA.getDNA());

					int hashValue = countHashBack(tmpDNA, zakladka);
					if (front[hashValue] != null) {

						if (random)
							arrayStartIndex = rand.nextInt(front[hashValue]
									.size());

						for (int i = arrayStartIndex; i < front[hashValue]
								.size()
								+ arrayStartIndex; i++) {
							int j = i % front[hashValue].size();

							DNAPiece a = front[hashValue].get(j);
							if (zbiorDanych.contains(a)) {
								if (debug)
									System.out.println(" + " + a.getDNA());
								tmpDNA.add(a, DNAPiece.BACK);
								zbiorDanych.remove(a);
								break;
							}
							if (j == front[hashValue].size() - 1) {
								front[hashValue] = null;
								break;
							}
						}
					} else {
						break;
					}

				}

				// Dokladanie z przodu
				if (debug)
					System.out.println("\nDo przodu");
				while (zbiorDanych.size() > 0) {
					if (debug)
						System.out.print(tmpDNA.getDNA());
					int hashValue = countHashFront(tmpDNA, zakladka);
					if (back[hashValue] != null) {

						if (random)
							arrayStartIndex = rand.nextInt(back[hashValue]
									.size());

						for (int i = arrayStartIndex; i < back[hashValue]
								.size()
								+ arrayStartIndex; i++) {
							int j = i % back[hashValue].size();

							DNAPiece a = back[hashValue].get(j);
							if (zbiorDanych.contains(a)) {
								if (debug)
									System.out.println(" + " + a.getDNA());
								tmpDNA.add(a, DNAPiece.FRONT);
								zbiorDanych.remove(a);
								break;
							}
							if (j == back[hashValue].size() - 1) {
								back[hashValue] = null;
								break;
							}
						}
					} else {
						break;
					}

				}
				if (debug)
					System.out.println("\nDodaję: " + tmpDNA.getDNA()
							+ "(zostało " + zbiorDanych.size() + ")");
				DNApieces.add(tmpDNA);
			}
			zbiorDanych = new ArrayList<DNAPiece>(DNApieces);
			zakladka--;

			if (debug) {
				try {
					System.in.read();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

		}

	}
	
	private void insertCollidingDNA() {
		ArrayList<DNAPiece> workingSet = new ArrayList<DNAPiece>();
		DNAPiece DNAbest = null;	
		ArrayList<DNAPiece> DNAcoliding = null;
		
		for (DNAPiece a : DNApieces)
			workingSet.add(a);

		for (int iteracja = 0; iteracja < workingSet.size(); iteracja++) {
			
			DNApieces = new ArrayList<DNAPiece>();
			DNAcoliding = new ArrayList<DNAPiece>();
			
			for (int i = 0; i < workingSet.size(); i++){
				if (i == iteracja)
					DNApieces.add(new DNAPiece(workingSet.get(i)));
				else
					DNAcoliding.add(new DNAPiece(workingSet.get(i)));
			}
			
			// Wstawienie tych ktore pasuje bez wydluzenia ciagu
			while (DNAcoliding.size() > 0) {
				DNAPiece a = DNAcoliding.get(0);
				if (DNApieces.get(0).getDNA().contains(a.getDNA())) {
					DNApieces.get(0).insertFullOK(a);
					DNAcoliding.remove(a);
				}
				// wstawianie w jak najlepszym miejscu tych ktore pozostaly
				// (jesli pozostaly)
				else {
					int usuniety = DNApieces.get(0).insertBestFit(DNAcoliding);
					DNAcoliding.remove(usuniety);
				}
			}
			if (DNAbest == null || DNAbest.getUseWordsCount() > DNApieces.get(0).getUseWordsCount())
				DNAbest = DNApieces.get(0);
			
			if (DNAbest.getDNALength() <= totalLength)
				break;
		}


		DNApieces = new ArrayList<DNAPiece>();
		DNApieces.add(DNAbest);

		int i = 0;


	}

	private void pruneDNA() {
		int actualLength = 0;
		DNAPiece DNA = null;
		DNAPiece bestDNA = null;

		// Gdy heurystyka zwroci kilka kawalkow DNA
		if (DNApieces.size() > 1) {
			// Sprawdzam czy wystarczy konkatenacja
			/*for (DNAPiece a : DNApieces)
				actualLength += a.getDNALength();*/

			//if (actualLength < totalLength) {
				DNA = new DNAPiece(singlePieceLength);
				for (DNAPiece a : DNApieces)
					DNA.add(a, DNAPiece.BACK);

				//DNApieces = new ArrayList<DNAPiece>();
				//DNApieces.add(DNA);
				
				bestDNA = DNA;
				
			//} else {
				/*DNA = new DNAPiece(singlePieceLength);
				actualLength = 0;
				ArrayList<DNAPiece> copyDNApieces = new ArrayList<DNAPiece>(
						DNApieces);
				// Wycinam z kopii DNApieces kawalki o najwiekszej gestosci slow
				// na jednostke dlugosci i konkatenuje poki nie osiagne dlugosci
				// >= totalLength
				while (copyDNApieces.size() > 0 && actualLength < totalLength) {
					float max = -1;
					int ind = 0;
					for (int i = 0; i < copyDNApieces.size(); i++) {
						float val = (float) copyDNApieces.get(i)
								.getUseWordsCount()
								/ copyDNApieces.get(i).getDNALength();
						if (val > max) {
							max = val;
							ind = i;
						}
					}

					// Gdy puste to wstawia obojetnie jak
					if (DNA.getUseWordsCount() == 0) {
						DNA.add(copyDNApieces.get(ind), DNAPiece.BACK);
					} // gdy juz cos jest to sprawdza z ktorej strony lepiej
					// wstawic
					else {
						int back = DNA.getZakladkaLength(
								copyDNApieces.get(ind), DNAPiece.BACK);
						int front = DNA.getZakladkaLength(copyDNApieces
								.get(ind), DNAPiece.FRONT);
						if (front > back)
							DNA.add(copyDNApieces.get(ind), DNAPiece.FRONT);
						else
							DNA.add(copyDNApieces.get(ind), DNAPiece.BACK);
					}
					copyDNApieces.remove(ind);

					actualLength = DNA.getDNALength();
				}
				DNApieces = new ArrayList<DNAPiece>();
				DNApieces.add(DNA);*/
				
				/*System.out.println("Mam do polaczenia " + DNApieces.size());
				
				ArrayList<DNAPiece> tmpDNA = null;
				
				int i = 0;
				while(i < DNApieces.size()){
					tmpDNA = new ArrayList<DNAPiece>();
					for (int j = 0; j < DNApieces.size(); j++){
						if (j == i)
							DNA = new DNAPiece(DNApieces.get(j));
						else
							tmpDNA.add(new DNAPiece(DNApieces.get(j)));
					}
					
					while (tmpDNA.size() > 0){
						int bestZakladka = -1;
						int bestIndex = -1;
						int bestSide = -1;
						
						for (int j = 0; j < tmpDNA.size(); j++){
							DNAPiece a = tmpDNA.get(j);
							if (DNA.getZakladkaLength(a, DNAPiece.BACK) > bestZakladka){
								bestZakladka = DNA.getZakladkaLength(a, DNAPiece.BACK);
								bestIndex = j;
								bestSide = DNAPiece.BACK;
							}
							
							if (DNA.getZakladkaLength(a, DNAPiece.FRONT) > bestZakladka){
								bestZakladka = DNA.getZakladkaLength(a, DNAPiece.FRONT);
								bestIndex = j;
								bestSide = DNAPiece.FRONT;
							}
						}
						
						DNA.add(tmpDNA.get(bestIndex), bestSide);
						tmpDNA.remove(bestIndex);
					}
					
					if (bestDNA == null || DNA.getUseWordsCount() > bestDNA.getUseWordsCount())
						bestDNA = DNA;
					
					i++;
				}
				

			} // koniec gdy nie starczyla konkatenacja

			*/
		} // kilka kawalkow
		else {
			bestDNA = DNApieces.get(0);			
		}


		//DNA = DNApieces.get(0);
		DNA = bestDNA;
		actualLength = DNA.getDNALength();
		while (actualLength > totalLength) {
			if (DNA.getZakladkaLength(DNAPiece.BACK) < DNA
					.getZakladkaLength(DNAPiece.FRONT))
				DNA.removeOne(DNAPiece.BACK);
			else
				DNA.removeOne(DNAPiece.FRONT);
			actualLength = DNA.getDNALength();
		}
		if (log)
			System.out.println("Skonczylem z DNA " + DNA.getDNA());
		
		System.out.println("Użyłem słów: " + DNA.getUseWordsCount() + "/"
				+ oligonukleotydy.size());

		System.out.println(fileName + ": Sumaryczna długość to: "
				+ actualLength + "/" + totalLength);

	}
}
